﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.IO;
using System.Collections;
using System.Xml;
using System.Diagnostics;
using TomanuExtensions;
using TomanuExtensions.Utils;

namespace AmiEmuLib.Configuration.KnownRomConfig
{
    /// <summary>
    /// Know roms. Loaded from roms.xml. Copied from WinUAE.
    /// </summary>
    [DebuggerDisplay("Count = {Count}")]
    public static class KnownRoms
    {
        /// <summary>
        /// List of known roms.
        /// </summary>
        private static List<KnownRom> m_roms = new List<KnownRom>();

        /// <summary>
        /// Static constructor.
        /// </summary>
        static KnownRoms()
        {
            using (var stream = StreamExtensions.FromResource(typeof(KnownRoms), "KnownRoms.xml"))
            {
                XmlReaderExtensions.ReadXml(stream, (reader) =>
                {
                    reader.ReadStartElement(typeof(KnownRoms).Name);

                    while (reader.IsStartElement())
                        m_roms.Add(new KnownRom(reader));

                    reader.ReadEndElement();
                });
                Rescan();
            }
        }

        /// <summary>
        /// Save known roms to xml. Only for development.
        /// </summary>
        public static void SaveToXml()
        {
            XmlWriterExtensions.WriteXml("KnownRoms.xml", (writer) =>
            {
                writer.WriteStartElement(typeof(KnownRoms).Name);

                foreach (var kr in m_roms)
                    kr.WriteXml(writer);

                writer.WriteEndElement();
            });
        }

        /// <summary>
        /// Search for known roms in Config.Instance.RomsDirectory and its subdirectories.
        /// if a_duplicates != null then its filled with duplicates file path.
        /// </summary>
        /// <param name="a_duplicates"></param>
        public static void Rescan(List<List<string>> a_duplicates = null)
        {
            foreach (var known_rom in m_roms)
                known_rom.FilePath = String.Empty;

            Dictionary<string, string> dict = new Dictionary<string, string>();
            RescanDirectory(Config.Instance.RomsDirectory, dict);

            var list = (from known_rom in m_roms
                        from pair in dict
                        where known_rom.SHA1 == pair.Value
                        select new
                        {
                            known_rom = known_rom,
                            fileInfo = pair.Key
                        });

            foreach (var g in list)
            {
                if (g.known_rom.FilePath == String.Empty)
                    g.known_rom.FilePath = g.fileInfo;
            }

            var unknowns = from pair in dict
                           where m_roms.All(kr => kr.SHA1 != pair.Value)
                           select pair;

            UnknownRoms.Clear();
            foreach (var pair in unknowns)
                UnknownRoms.Add(new UnknownRom() { FilePath = pair.Key, SHA1 = pair.Value });

            if (a_duplicates != null)
            {
                a_duplicates.Clear();
                a_duplicates.AddRange((from pair in dict
                                       group pair by pair.Value into gr
                                       where gr.Count() > 1
                                       select (from p in gr
                                               select p.Key).ToList()).ToList());
            }
        }

        /// <summary>
        /// Search for known roms in directory and its subdirectories.
        /// </summary>
        /// <param name="a_dir"></param>
        /// <param name="dict"></param>
        private static void RescanDirectory(string a_dir, Dictionary<string, string> dict)
        {
            DirectoryInfo di = new DirectoryInfo(a_dir);

            if (!di.Exists)
                return;

            foreach (var file in di.GetFiles())
                RescanFile(file.FullName, dict);
            foreach (var dir in di.GetDirectories())
                RescanDirectory(dir.FullName, dict);
        }

        /// <summary>
        /// Check if a_file is known rom.
        /// </summary>
        /// <param name="a_file"></param>
        /// <param name="dict"></param>
        private static void RescanFile(string a_file, Dictionary<string, string> a_dict)
        {
            string sha1 = Hash.CalculateSHA1ForFile(a_file);

            if (sha1 == null)
                return;

            a_dict.Add(a_file, sha1);
        }

        /// <summary>
        /// List of known roms.
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<KnownRom> Roms
        {
            get
            {
                return m_roms;
            }
        }

        /// <summary>
        /// Get most fitted available dafult rom.
        /// </summary>
        /// <param name="a_type"></param>
        /// <returns></returns>
        public static KnownRom GetAvailableDefaultKnownRom(RomType a_type)
        {
            var roms2 = from kr in m_roms
                        where kr.RomTypes.HasFlag(a_type)
                        where kr.Exists
                        where kr.Priority > -1
                        orderby kr.Priority
                        select kr;

            if (roms2.Any())
                return roms2.Last();
            else
                return null;
        }
    }
}
